package org.fglxy.mockserver.util;

import java.util.function.BiConsumer;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import jdk.nashorn.api.scripting.ScriptObjectMirror;

@SuppressWarnings("restriction")
public class JSInvoker {
	
	private static String WRAPPER_PREFIX = "function enter(){return ";
	private static String WRAPPER_SUFFIX = "}";
	
	private ScriptEngine engine;
	
	private String script;
	
	private ScriptObjectMirror rootResult;
	
	private Object curResult;
	
	private JSInvoker(String script) throws ScriptException, NoSuchMethodException {
		this.engine = (new ScriptEngineManager()).getEngineByName("nashorn");
		this.script = wrapperScript(script);
		init();
	}
	
	public JSInvoker(ScriptObjectMirror script) {
		this.engine = (new ScriptEngineManager()).getEngineByName("nashorn");
		this.rootResult = script;
	}
	
	private void init() throws ScriptException, NoSuchMethodException {
		this.engine.eval(this.script);
		this.rootResult = (ScriptObjectMirror) ((Invocable)this.engine).invokeFunction("enter");
		this.curResult = this.rootResult;
	}
	/**
	 * 
	 * @param script 只接收json对象
	 * @return
	 * @throws ScriptException 
	 */
	public static JSInvoker getInstance(String script) throws ScriptException {
		try {
			return new JSInvoker(script);
		} catch (Exception e) {
			throw new ScriptException(e);
		}
	}
	
	public static JSInvoker getInstance(ScriptObjectMirror script) {
		return new JSInvoker(script);
	}
	
	public JSInvoker callFunction(String name, Object... args) throws NoSuchMethodException {
		if (!(this.curResult instanceof ScriptObjectMirror)) {
			return this;
		}
		ScriptObjectMirror script = (ScriptObjectMirror) this.curResult;
		if (!script.hasMember(name)) {
			throw new NoSuchMethodException("no such js method");
		}
		this.curResult = script.callMember(name, args);
		return this;
	}
	
	public void forEach(BiConsumer<String, Object> action) {
		if (!(this.curResult instanceof ScriptObjectMirror)) {
			return;
		}
		ScriptObjectMirror script = (ScriptObjectMirror) this.curResult;
		if (!script.isArray()) {
			return;
		}
		script.forEach(action);
	}
	
	public String getResult() {
		String result = this.curResult.toString();
		this.curResult = this.rootResult;
		return result;
	}
	
	private static String wrapperScript(String script) {
		StringBuilder builder = new StringBuilder(WRAPPER_PREFIX);
		builder.append(script);
		builder.append(WRAPPER_SUFFIX);
		return builder.toString();
	}

	public void reset() {
		this.curResult = this.rootResult;
	}

	public boolean hasFunction(String functionName) {
		return this.rootResult.containsKey(functionName);
	}
}
